#include "CSV.h"

auto NahidaProject::CSV::CSV::Quote(char c) noexcept -> CSV&& {
	quote = c;
	return std::move(*this);
}

auto NahidaProject::CSV::CSV::Delimiter(char c) noexcept -> CSV&& {
	delimiter = c;
	return std::move(*this);
}

auto NahidaProject::CSV::CSV::Terminator(char c) noexcept -> CSV&& {
	terminator = static_cast<Term>(c);
	return std::move(*this);
}

auto NahidaProject::CSV::CSV::Empty() -> bool {
	return state == State::EMPTY;
}

auto NahidaProject::CSV::CSV::Position() const -> std::streamoff {
	return scanPosition + static_cast<std::streamoff>(cursor);
}

auto NahidaProject::CSV::CSV::NextField() -> Field {
	if (Empty()) {
		return Field(FieldType::CSV_END);
	}
	fieldBuffer.clear();

	for (;;) {
		char* maybeToken = TopToken();

		// If we're out of tokens to read return whatever's left in the
		// field and row buffers. If there's nothing left, return null.
		if (maybeToken == nullptr) {
			state = State::EMPTY;
			return !fieldBuffer.empty() ? Field(std::move(fieldBuffer)) : Field(FieldType::CSV_END);
		}

		// Parsing the CSV is done using a finite state machine
		char c = *maybeToken;
		switch (state) {
		case State::START_OF_FIELD:
			cursor++;
			if (c == terminator) {
				HandleCRLF(c);
				state = State::END_OF_ROW;
				return Field(std::move(fieldBuffer));
			}

			if (c == quote) {
				state = State::IN_QUOTED_FIELD;
			}
			else if (c == delimiter) {
				return Field(std::move(fieldBuffer));
			}
			else {
				state = State::IN_FIELD;
				fieldBuffer += c;
			}

			break;

		case State::IN_FIELD:
			cursor++;
			if (c == terminator) {
				HandleCRLF(c);
				state = State::END_OF_ROW;
				return Field(std::move(fieldBuffer));
			}

			if (c == delimiter) {
				state = State::START_OF_FIELD;
				return Field(std::move(fieldBuffer));
			}

			fieldBuffer += c;
			break;

		case State::IN_QUOTED_FIELD:
			cursor++;
			if (c == quote) {
				state = State::IN_ESCAPED_QUOTE;
			}
			else {
				fieldBuffer += c;
			}

			break;

		case State::IN_ESCAPED_QUOTE:
			cursor++;
			if (c == terminator) {
				HandleCRLF(c);
				state = State::END_OF_ROW;
				return Field(std::move(fieldBuffer));
			}

			if (c == quote) {
				state = State::IN_QUOTED_FIELD;
				fieldBuffer += c;
			}
			else if (c == delimiter) {
				state = State::START_OF_FIELD;
				return Field(std::move(fieldBuffer));
			}
			else {
				state = State::IN_FIELD;
				fieldBuffer += c;
			}

			break;

		case State::END_OF_ROW:
			state = State::START_OF_FIELD;
			return Field(FieldType::ROW_END);

		case State::EMPTY:
			throw std::logic_error("You goofed");
		}
	}
}

void NahidaProject::CSV::CSV::HandleCRLF(const char c) {
	if (terminator != Term::CRLF || c != '\r') {
		return;
	}

	char* token = TopToken();
	if ((token != nullptr) && *token == '\n') {
		cursor++;
	}
}

auto NahidaProject::CSV::CSV::TopToken() -> char* {
	if (endOfFile && cursor == bytesRead) {
		return nullptr;
	}

	if (cursor == bytesRead) {
		FillBuffer();
		if (bytesRead == 0) {
			return nullptr;
		}
	}
	return &inputBuffer[cursor];
}

void NahidaProject::CSV::CSV::FillBuffer() {
	input->read(inputBuffer.data(), INPUTBUF_CAP);
	bytesRead = static_cast<size_t>(input->gcount());
	endOfFile = input->eof();
	cursor = 0;

	if (scanPosition == 0 && bytesRead >= 3 && inputBuffer[0] == '\xEF' && inputBuffer[1] == '\xBB' && inputBuffer[2] == '\xBF') {
		if (bytesRead > 3) {
			cursor = 3;
		}
		else {
			bytesRead = 0;
		}
	}

	scanPosition += static_cast<std::streamoff>(bytesRead);
}

auto NahidaProject::CSV::CSV::Iterator::operator++() -> Iterator& {
	next();
	return *this;
}

auto NahidaProject::CSV::CSV::Iterator::operator++(int) -> Iterator {
	Iterator i = (*this);
	++(*this);
	return i;
}

auto NahidaProject::CSV::CSV::Iterator::operator==(const Iterator& other) const -> bool {
	return currentRow == other.currentRow && row.size() == other.row.size();
}

auto NahidaProject::CSV::CSV::Iterator::operator!=(const Iterator& other) const -> bool {
	return !(*this == other);
}

auto NahidaProject::CSV::CSV::Iterator::operator*() const -> Reference {
	return row;
}

auto NahidaProject::CSV::CSV::Iterator::operator->() const -> Pointer {
	return &row;
}

void NahidaProject::CSV::CSV::Iterator::next() {
	ValueType::size_type numberOfFields = 0;
	for (;;) {
		auto field = parser->NextField();
		switch (field.type) {
		case FieldType::CSV_END:
			if (numberOfFields < row.size()) {
				row.resize(numberOfFields);
			}
			currentRow = -1;
			return;
		case FieldType::ROW_END:
			if (numberOfFields < row.size()) {
				row.resize(numberOfFields);
			}
			currentRow++;
			return;
		case FieldType::DATA:
			if (numberOfFields < row.size()) {
				row[numberOfFields] = std::move(field.data);
			}
			else {
				row.push_back(std::move(field.data));
			}
			numberOfFields++;
		}
	}
}